class Compile {
  constructor(vm) {
    // 获取根节点
    this.el = vm.$el;
    // 保存 Vue 实例
    this.vm = vm;
    // 编译模板
    this.compile(this.el);
  }

  // 编译模版
  compile(el) {
    // 获取根节点的子节点列表
    const childNodes = el.childNodes;

    Array.from(childNodes).forEach((node) => {
      if (node.nodeType === 1) {
        // 元素节点
        this.compileElement(node);
      } else if (node.nodeType === 3) {
        // 文本节点
        this.compileText(node);
      }

      // 递归编译子节点
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }

  // 编译元素节点
  compileElement(node) {
    // 获取元素节点的属性列表
    const attrs = node.attributes;
    
    Array.from(attrs).forEach((attr) => {
      const attrName = attr.name;

      if (attrName.startsWith("v-")) {
        // 匹配指令
        const dir = attrName.substring(2); // 获取指令名称
        const exp = attr.value;
        this[dir] && this[dir](node, exp); // 调用对应的指令函数
      }
    });
  }

  // 编译文本节点
  compileText(node) {
    const reg = /\{\{(.+?)\}\}/;
    // 获取插值语法中的表达式
    const exp = node.textContent;
    if (reg.test(exp)) {
      // 将插值语法替换为表达式的值
      node.textContent = this.getVMValue(exp);
    }
  }

  getVMValue(exp) {
    let value = this.vm;
    exp.split(".").forEach((key) => {
      value = value[key];
    });
    return value;
  }

  // v-model 指令
  model(node, exp) {
    this.bind(node, exp, "model");

    node.addEventListener("input", (e) => {
      const newValue = e.target.value;
      this.setVMValue(exp, newValue);
    });
  }

  // v-bind 指令
  bind(node, exp, dir) {
    const updaterFn = this[`${dir}Updater`];
    updaterFn && updaterFn(node, this.getVMValue(exp));

    new Watcher(this.vm, exp, (value) => {
      updaterFn && updaterFn(node, value);
    });
  }

  // model 指令更新视图
  modelUpdater(node, value) {
    node.value = value;
  }

  // v-text 指令
  text(node, exp) {
    this.bind(node, exp, "text");
  }

  // text 指令更新视图
  textUpdater(node, value) {
    node.textContent = value;
  }

  setVMValue(exp, value) {
    let vm = this.vm;
    const keys = exp.split(".");
    keys.forEach((key, index) => {
      if (index < keys.length - 1) {
        vm = vm[key];
      } else {
        vm[key] = value;
      }
    });
  }
}
